package com.yupi.springbootinit.service.impl;

import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.model.dto.processsheet.ProcessSheetExcelStepDTO;
import com.yupi.springbootinit.service.DocumentParseService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.*;

/**
 * DocumentParseService 实现（支持 Excel 和 Word）
 * 完整复用原 ProcessSheetServiceImpl 的解析逻辑与行为，新增 DOCX 支持。
 */
@Service
@Slf4j
public class DocumentParseServiceImpl implements DocumentParseService {

    @Override
    public List<ProcessSheetExcelStepDTO> parse(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件不能为空");
        }
        String filename = file.getOriginalFilename();
        if (filename == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件名不能为空");
        }
        
        // 根据文件扩展名分流处理
        String lowerFilename = filename.toLowerCase();
        if (lowerFilename.endsWith(".docx")) {
            return parseWordDocument(file, filename);
        } else if (lowerFilename.endsWith(".xlsx") || lowerFilename.endsWith(".xls")) {
            return parseExcelDocument(file, filename);
        } else {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "仅支持 .xlsx / .xls / .docx 文件");
        }
    }

    @Override
    public List<ProcessSheetExcelStepDTO> parse(File file, String originalFilename) {
        if (file == null || !file.exists()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "文件不存在");
        }
        String filename = originalFilename != null ? originalFilename : file.getName();
        
        // 根据文件扩展名分流处理
        String lowerFilename = filename.toLowerCase();
        if (lowerFilename.endsWith(".docx")) {
            return parseWordDocumentFromFile(file, filename);
        } else if (lowerFilename.endsWith(".xlsx") || lowerFilename.endsWith(".xls")) {
            return parseExcelDocumentFromFile(file, filename);
        } else {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "仅支持 .xlsx / .xls / .docx 文件");
        }
    }

    /**
     * 解析 Excel 文档（MultipartFile）
     */
    private List<ProcessSheetExcelStepDTO> parseExcelDocument(MultipartFile file, String filename) {
        try (InputStream is = file.getInputStream();
             Workbook workbook = WorkbookFactory.create(is)) {
            return parseFromWorkbook(workbook, filename);
        } catch (BusinessException be) {
            throw be;
        } catch (Exception e) {
            log.error("解析工艺卡 Excel 失败: file={}", filename, e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "Excel 解析失败，请检查文件内容与格式");
        }
    }

    /**
     * 解析 Excel 文档（本地文件）
     */
    private List<ProcessSheetExcelStepDTO> parseExcelDocumentFromFile(File file, String filename) {
        try (FileInputStream fis = new FileInputStream(file);
             Workbook workbook = WorkbookFactory.create(fis)) {
            return parseFromWorkbook(workbook, filename);
        } catch (BusinessException be) {
            throw be;
        } catch (Exception e) {
            log.error("解析工艺卡 Excel 失败: file={}", filename, e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "Excel 解析失败，请检查文件内容与格式");
        }
    }

    /**
     * 解析 Word 文档（MultipartFile）
     */
    private List<ProcessSheetExcelStepDTO> parseWordDocument(MultipartFile file, String filename) {
        try (InputStream is = file.getInputStream();
             XWPFDocument document = new XWPFDocument(is)) {
            return parseFromWordDocument(document, filename);
        } catch (BusinessException be) {
            throw be;
        } catch (Exception e) {
            log.error("解析工艺卡 Word 失败: file={}", filename, e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "Word 解析失败，请检查文件内容与格式");
        }
    }

    /**
     * 解析 Word 文档（本地文件）
     */
    private List<ProcessSheetExcelStepDTO> parseWordDocumentFromFile(File file, String filename) {
        try (FileInputStream fis = new FileInputStream(file);
             XWPFDocument document = new XWPFDocument(fis)) {
            return parseFromWordDocument(document, filename);
        } catch (BusinessException be) {
            throw be;
        } catch (Exception e) {
            log.error("解析工艺卡 Word 失败: file={}", filename, e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "Word 解析失败，请检查文件内容与格式");
        }
    }

    /**
     * 统一的 Workbook 解析逻辑：
     * - 多 sheet 支持
     * - 识别竖排/合并单元格标签
     * - 邻接内容块抽取
     * - “工序说明/检验要求/工序名称”处理
     */
    private List<ProcessSheetExcelStepDTO> parseFromWorkbook(Workbook workbook, String filename) {
        List<ProcessSheetExcelStepDTO> result = new ArrayList<>();
        DataFormatter formatter = new DataFormatter();

        int sheetCount = workbook.getNumberOfSheets();
        for (int s = 0; s < sheetCount; s++) {
            Sheet sheet = workbook.getSheetAt(s);
            if (sheet == null) {
                continue;
            }

            // 扫描全表，定位所有标签
            List<LabelPos> labels = findLabels(sheet, formatter);
            labels.sort((a, b) -> Integer.compare(a.row, b.row));

            // 将 “工序说明” 与其后的最近 “检验要求” 配对
            int i = 0;
            while (i < labels.size()) {
                LabelPos lp = labels.get(i);
                if (!"PROC_DESC".equals(lp.kind)) {
                    i++;
                    continue;
                }
                LabelPos iq = null;
                for (int j = i + 1; j < labels.size(); j++) {
                    if ("QC_REQ".equals(labels.get(j).kind)) {
                        iq = labels.get(j);
                        break;
                    }
                    if ("PROC_DESC".equals(labels.get(j).kind)) {
                        break;
                    }
                }

                String processDesc = extractAdjacentBlockText(sheet, lp, formatter);
                String inspectReq = iq != null ? extractAdjacentBlockText(sheet, iq, formatter) : null;

                if (StringUtils.isNotBlank(processDesc) || StringUtils.isNotBlank(inspectReq)) {
                    ProcessSheetExcelStepDTO dto = new ProcessSheetExcelStepDTO();
                    dto.setSequenceNo(result.size() + 1);

                    // 工序名称：查找附近的“工序名称/工序名”标签，优先取右侧值，其次取下方块
                    String processName = null;
                    LabelPos nameLabel = null;
                    // 向上寻找最近的名称标签，限定6行内，若遇到上一段工序说明则停止
                    for (int k = i; k >= 0; k--) {
                        LabelPos p = labels.get(k);
                        if ("PROC_DESC".equals(p.kind) && p.row < lp.row) break;
                        if ("PROC_NAME".equals(p.kind) && (lp.row - p.row) <= 6) {
                            nameLabel = p;
                            break;
                        }
                    }
                    // 未找到则向下在下一段工序说明之前寻找，限定3行内
                    if (nameLabel == null) {
                        for (int k = i + 1; k < labels.size(); k++) {
                            LabelPos p = labels.get(k);
                            if ("PROC_DESC".equals(p.kind)) break;
                            if ("PROC_NAME".equals(p.kind) && (p.row - lp.row) <= 3) {
                                nameLabel = p;
                                break;
                            }
                        }
                    }
                    if (nameLabel != null) {
                        processName = extractAdjacentBlockText(sheet, nameLabel, formatter);
                    }
                    dto.setProcessName(StringUtils.trim(processName));

                    dto.setProcessDescription(StringUtils.trim(processDesc));
                    dto.setInspectionRequirement(StringUtils.trim(inspectReq));
                    dto.setIsInspectionRequired(StringUtils.isBlank(inspectReq) ? 0 : 1);
                    result.add(dto);
                }

                if (iq != null) {
                    i = labels.indexOf(iq) + 1;
                } else {
                    i = i + 1;
                }
            }
        }

        if (result.isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "未解析到任何工序页，请检查模板与填写内容（工序说明/检验要求）");
        }

        log.info("Excel 解析完成：文件={}，总步骤数={}", filename, result.size());
        return result;
    }

    /**
     * 寻找所有标签位置（工序说明 / 检验要求 / 工序名称），适配垂直文字与合并单元格。
     */
    private List<LabelPos> findLabels(Sheet sheet, DataFormatter formatter) {
        List<LabelPos> labels = new ArrayList<>();
        Set<String> seen = new HashSet<>();
        int lastRow = sheet.getLastRowNum();
        for (int r = 0; r <= lastRow; r++) {
            Row row = sheet.getRow(r);
            if (row == null) continue;
            short lastCell = row.getLastCellNum();
            if (lastCell < 0) continue;
            for (int c = 0; c < lastCell; c++) {
                String raw = getMergedAwareCellString(sheet, r, c, formatter);
                if (StringUtils.isBlank(raw)) continue;
                String t = normalize(raw);

                boolean isProc = containsAny(t, "工序说明", "工艺要求");
                boolean isQc = !isProc && containsAny(t, "检验要求", "质检内容");
                boolean isName = !isProc && !isQc && containsAny(t, "工序名称", "工序名");
                if (!isProc && !isQc && !isName) continue;

                CellRangeAddress reg = findMergedRegion(sheet, r, c);
                int fr = reg != null ? reg.getFirstRow() : r;
                int fc = reg != null ? reg.getFirstColumn() : c;
                int lr = reg != null ? reg.getLastRow() : r;
                int lc = reg != null ? reg.getLastColumn() : c;

                String key = fr + ":" + fc + ":" + lr + ":" + lc + ":" + (isProc ? "P" : (isQc ? "Q" : "N"));
                if (seen.contains(key)) continue;
                seen.add(key);

                LabelPos lp = new LabelPos();
                lp.row = fr;
                lp.col = fc;
                lp.kind = isProc ? "PROC_DESC" : (isQc ? "QC_REQ" : "PROC_NAME");
                lp.region = reg != null ? new CellRangeAddress(fr, lr, fc, lc) : null;
                labels.add(lp);
            }
        }
        return labels;
    }

    /**
     * 从标签下方的大块内容区域提取文本（限定在标签列范围内）。
     */
    private String extractAdjacentBlockText(Sheet sheet, LabelPos label, DataFormatter formatter) {
        int baseRow = (label.region != null ? label.region.getLastRow() : label.row) + 1;
        int fc = (label.region != null ? label.region.getFirstColumn() : label.col);
        int lc = (label.region != null ? label.region.getLastColumn() : label.col);
        int lastRow = sheet.getLastRowNum();

        for (int r = baseRow; r <= Math.min(baseRow + 5, lastRow); r++) {
            for (int c = fc; c <= lc; c++) {
                String v = getMergedAwareCellString(sheet, r, c, formatter);
                if (StringUtils.isNotBlank(v) && !isLabelText(normalize(v))) {
                    return v;
                }
            }
        }

        StringBuilder sb = new StringBuilder();
        int emptyRun = 0;
        for (int r = baseRow; r <= Math.min(baseRow + 30, lastRow); r++) {
            String rowText = null;
            for (int c = fc; c <= lc; c++) {
                String v = getMergedAwareCellString(sheet, r, c, formatter);
                if (StringUtils.isNotBlank(v)) {
                    String n = normalize(v);
                    if (isLabelText(n)) {
                        rowText = null;
                        break;
                    }
                    rowText = v.trim();
                    break;
                }
            }

            if (rowText == null) {
                emptyRun++;
                if (emptyRun >= 2 && sb.length() > 0) {
                    break;
                }
                continue;
            }

            emptyRun = 0;
            if (sb.length() > 0) sb.append("\n");
            sb.append(rowText);
        }
        return StringUtils.trimToNull(sb.toString());
    }

    /**
     * 优先从标签右侧取值（同一行范围内），若无则回落到标签下方块。
     */
    @SuppressWarnings("unused")
    private String extractRightOrBelowText(Sheet sheet, LabelPos label, DataFormatter formatter) {
        int fr = (label.region != null ? label.region.getFirstRow() : label.row);
        int lr = (label.region != null ? label.region.getLastRow() : label.row);
        int lc = (label.region != null ? label.region.getLastColumn() : label.col);
        int lastColGuess = lc + 5;

        for (int r = fr; r <= lr; r++) {
            Row row = sheet.getRow(r);
            if (row == null) continue;
            short lastCellNum = row.getLastCellNum();
            int lastCol = lastCellNum >= 0 ? lastCellNum - 1 : lastColGuess;
            for (int c = lc + 1; c <= Math.min(lc + 3, lastCol); c++) {
                String v = getMergedAwareCellString(sheet, r, c, formatter);
                if (StringUtils.isNotBlank(v) && !isLabelText(normalize(v))) {
                    return v;
                }
            }
        }
        return extractAdjacentBlockText(sheet, label, formatter);
    }

    private String getMergedAwareCellString(Sheet sheet, int rowIdx, int colIdx, DataFormatter formatter) {
        if (rowIdx < 0 || colIdx < 0) return null;
        Row row = sheet.getRow(rowIdx);
        if (row == null) return null;

        CellRangeAddress reg = findMergedRegion(sheet, rowIdx, colIdx);
        if (reg != null) {
            Row topRow = sheet.getRow(reg.getFirstRow());
            if (topRow != null) {
                Cell topLeft = topRow.getCell(reg.getFirstColumn());
                return formatCell(topLeft, formatter);
            }
        }

        Cell cell = row.getCell(colIdx);
        return formatCell(cell, formatter);
    }

    private CellRangeAddress findMergedRegion(Sheet sheet, int rowIdx, int colIdx) {
        for (CellRangeAddress range : sheet.getMergedRegions()) {
            if (range.isInRange(rowIdx, colIdx)) {
                return range;
            }
        }
        return null;
    }

    private String normalize(String s) {
        if (s == null) return null;
        return s.replaceAll("\\s+", "")
                .replace("：", ":")
                .replace("；", ";")
                .trim();
    }

    private boolean containsAny(String src, String... keys) {
        if (StringUtils.isBlank(src)) return false;
        for (String k : keys) {
            if (src.contains(k)) return true;
        }
        return false;
    }

    private boolean isLabelText(String t) {
        if (StringUtils.isBlank(t)) return false;
        return containsAny(t, "工序说明", "工艺说明", "操作说明", "检验要求", "检验方式", "检验项目", "验收标准", "工序名称", "工序名");
    }

    private String formatCell(Cell cell, DataFormatter formatter) {
        if (cell == null) return null;
        try {
            return StringUtils.trimToNull(formatter.formatCellValue(cell));
        } catch (Exception ignored) {
            return safeCellString(cell);
        }
    }

    private String safeCellString(Cell cell) {
        if (cell == null) return null;
        try {
            return switch (cell.getCellType()) {
                case STRING -> cell.getStringCellValue();
                case NUMERIC -> new DataFormatter().formatCellValue(cell);
                case BOOLEAN -> String.valueOf(cell.getBooleanCellValue());
                case FORMULA -> new DataFormatter().formatCellValue(cell);
                default -> null;
            };
        } catch (Exception e) {
            return null;
        }
    }

    private static class LabelPos {
        int row;
        int col;
        String kind; // PROC_DESC / QC_REQ / PROC_NAME
        CellRangeAddress region;
    }

    /**
     * 解析 Word 文档（统一入口）
     */
    private List<ProcessSheetExcelStepDTO> parseFromWordDocument(XWPFDocument document, String filename) {
        List<ProcessSheetExcelStepDTO> result = new ArrayList<>();
        
        // 获取所有表格
        List<XWPFTable> tables = document.getTables();
        if (tables.isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "Word文档中未找到表格");
        }
        
        log.info("开始解析Word文档，表格数量: {}", tables.size());
        
        // 遍历所有表格
        for (int tableIndex = 0; tableIndex < tables.size(); tableIndex++) {
            XWPFTable table = tables.get(tableIndex);
            List<ProcessSheetExcelStepDTO> tableResults = parseTableFromWord(table, tableIndex, filename);
            result.addAll(tableResults);
        }
        
        if (result.isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "未解析到任何工序页，请检查模板与填写内容（工序说明/检验要求）");
        }
        
        // 重新编号
        for (int i = 0; i < result.size(); i++) {
            result.get(i).setSequenceNo(i + 1);
        }
        
        log.info("Word 解析完成：文件={}，总步骤数={}", filename, result.size());
        return result;
    }
    
    /**
     * 解析单个表格
     */
    private List<ProcessSheetExcelStepDTO> parseTableFromWord(XWPFTable table, int tableIndex, String filename) {
        List<ProcessSheetExcelStepDTO> results = new ArrayList<>();
        
        List<XWPFTableRow> rows = table.getRows();
        if (rows.isEmpty()) {
            return results;
        }
        
        // 定位标签位置
        Map<String, CellPosition> labelPositions = findWordTableLabels(table);
        
        log.debug("表格{}中找到标签: {}", tableIndex, labelPositions.keySet());
        
        // 检查是否包含工序相关标签
        CellPosition processDescPos = labelPositions.get("工序说明");
        CellPosition inspectionPos = labelPositions.get("检验要求");
        CellPosition processNamePos = labelPositions.get("工序名称");
        if (processNamePos == null) {
            processNamePos = labelPositions.get("工序名");
        }
        
        // 如果表格中包含工序相关信息，就解析为一个步骤
        if (processDescPos != null || inspectionPos != null) {
            String processDesc = "";
            String inspectionReq = "";
            String processName = "";
            
            if (processDescPos != null) {
                processDesc = extractContentBelowLabel(table, processDescPos);
            }
            
            if (inspectionPos != null) {
                inspectionReq = extractContentBelowLabel(table, inspectionPos);
            }
            
            if (processNamePos != null) {
                processName = extractContentOnlyBelow(table, processNamePos);
            }
            
            // 如果有有效内容，就创建一个步骤
            if (StringUtils.isNotBlank(processDesc) || StringUtils.isNotBlank(inspectionReq)) {
                ProcessSheetExcelStepDTO dto = new ProcessSheetExcelStepDTO();
                dto.setSequenceNo(results.size() + 1);
                dto.setProcessName(StringUtils.trim(processName));
                dto.setProcessDescription(StringUtils.trim(processDesc));
                dto.setInspectionRequirement(StringUtils.trim(inspectionReq));
                dto.setIsInspectionRequired(StringUtils.isBlank(inspectionReq) ? 0 : 1);
                results.add(dto);
                
                log.debug("表格{}解析出工序: 名称=[{}], 说明=[{}], 检验=[{}]", 
                    tableIndex, processName, 
                    processDesc.length() > 20 ? processDesc.substring(0, 20) + "..." : processDesc,
                    inspectionReq.length() > 20 ? inspectionReq.substring(0, 20) + "..." : inspectionReq);
            }
        }
        
        return results;
    }
    
    /**
     * 在Word表格中查找标签位置
     */
    private Map<String, CellPosition> findWordTableLabels(XWPFTable table) {
        Map<String, CellPosition> positions = new HashMap<>();
        
        List<XWPFTableRow> rows = table.getRows();
        for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
            XWPFTableRow row = rows.get(rowIndex);
            List<XWPFTableCell> cells = row.getTableCells();
            
            for (int colIndex = 0; colIndex < cells.size(); colIndex++) {
                XWPFTableCell cell = cells.get(colIndex);
                String cellText = getCellTextFromWord(cell);
                String normalizedText = normalizeText(cellText);
                
                // 匹配各种标签 - 使用更松松的匹配策略
                if (containsAny(normalizedText, "工序说明", "工艺要求", "工艺说明", "操作说明")) {
                    positions.put("工序说明", new CellPosition(rowIndex, colIndex));
                    log.debug("找到工序说明标签: [行{}, 列{}] = {}", rowIndex, colIndex, cellText);
                } else if (containsAny(normalizedText, "检验要求", "质检内容", "检验内容", "检验方式", "验收标准")) {
                    positions.put("检验要求", new CellPosition(rowIndex, colIndex));
                    log.debug("找到检验要求标签: [行{}, 列{}] = {}", rowIndex, colIndex, cellText);
                } else if (containsAny(normalizedText, "工序名称", "工序名")) {
                    positions.put("工序名称", new CellPosition(rowIndex, colIndex));
                    log.debug("找到工序名称标签: [行{}, 列{}] = {}", rowIndex, colIndex, cellText);
                }
            }
        }
        
        return positions;
    }
    
    /**
     * 只从标签下方提取内容（不检查右侧）
     */
    private String extractContentOnlyBelow(XWPFTable table, CellPosition labelPos) {
        List<XWPFTableRow> rows = table.getRows();
        
        // 直接在下方查找，不检查右侧
        for (int rowIndex = labelPos.row + 1; rowIndex < Math.min(labelPos.row + 10, rows.size()); rowIndex++) {
            XWPFTableRow row = rows.get(rowIndex);
            List<XWPFTableCell> cells = row.getTableCells();
            
            // 在标签列及其右侧几列中查找
            for (int col = labelPos.col; col < Math.min(labelPos.col + 3, cells.size()); col++) {
                if (col < cells.size()) {
                    String content = getCellTextFromWord(cells.get(col));
                    if (StringUtils.isNotBlank(content) && !isLabelText(normalizeText(content))) {
                        log.debug("从工序名称标签[行{}, 列{}]的下方[行{}, 列{}]提取到内容: {}", 
                            labelPos.row, labelPos.col, rowIndex, col, content);
                        return content.trim();
                    }
                }
            }
        }
        
        return "";
    }
    
    /**
     * 提取标签下方的内容
     */
    private String extractContentBelowLabel(XWPFTable table, CellPosition labelPos) {
        List<XWPFTableRow> rows = table.getRows();
        
        // 先尝试在同一行的右侧查找
        if (labelPos.row < rows.size()) {
            XWPFTableRow currentRow = rows.get(labelPos.row);
            List<XWPFTableCell> cells = currentRow.getTableCells();
            
            // 尝试右侧几个单元格
            for (int col = labelPos.col + 1; col < Math.min(labelPos.col + 3, cells.size()); col++) {
                String content = getCellTextFromWord(cells.get(col));
                if (StringUtils.isNotBlank(content) && !isLabelText(normalizeText(content))) {
                    return content.trim();
                }
            }
        }
        
        // 如果右侧没有，就在下方查找
        for (int rowIndex = labelPos.row + 1; rowIndex < Math.min(labelPos.row + 10, rows.size()); rowIndex++) {
            XWPFTableRow row = rows.get(rowIndex);
            List<XWPFTableCell> cells = row.getTableCells();
            
            // 在标签列及其右侧几列中查找
            for (int col = labelPos.col; col < Math.min(labelPos.col + 3, cells.size()); col++) {
                if (col < cells.size()) {
                    String content = getCellTextFromWord(cells.get(col));
                    if (StringUtils.isNotBlank(content) && !isLabelText(normalizeText(content))) {
                        return content.trim();
                    }
                }
            }
        }
        
        return "";
    }
    
    /**
     * 提取标签右侧或下方的内容
     */
    private String extractContentRightOrBelow(XWPFTable table, CellPosition labelPos) {
        List<XWPFTableRow> rows = table.getRows();
        
        // 先在同一行的右侧查找
        if (labelPos.row < rows.size()) {
            XWPFTableRow currentRow = rows.get(labelPos.row);
            List<XWPFTableCell> cells = currentRow.getTableCells();
            
            for (int col = labelPos.col + 1; col < cells.size(); col++) {
                String content = getCellTextFromWord(cells.get(col));
                if (StringUtils.isNotBlank(content) && !isLabelText(normalizeText(content))) {
                    return content.trim();
                }
            }
        }
        
        // 如果右侧没有，就在下方查找
        return extractContentBelowLabel(table, labelPos);
    }
    
    /**
     * 获取Word单元格文本
     */
    private String getCellTextFromWord(XWPFTableCell cell) {
        if (cell == null) {
            return "";
        }
        
        try {
            String text = cell.getText();
            return text != null ? text.trim() : "";
        } catch (Exception e) {
            log.warn("获取Word单元格文本失败", e);
            return "";
        }
    }
    
    /**
     * 文本标准化
     */
    private String normalizeText(String text) {
        if (text == null) {
            return "";
        }
        return text.replaceAll("\\s+", "")
                .replace("：", ":")
                .replace("；", ";")
                .trim();
    }
    
    /**
     * 单元格位置类
     */
    private static class CellPosition {
        int row;
        int col;
        
        CellPosition(int row, int col) {
            this.row = row;
            this.col = col;
        }
        
        @Override
        public String toString() {
            return "CellPosition{row=" + row + ", col=" + col + "}";
        }
    }
}